package com.kevin.mapping.script.parser;

import com.kevin.mapping.script.exception.ParserException;
import com.kevin.mapping.script.lexer.*;
import com.kevin.mapping.script.node.logical.*;
import com.kevin.mapping.script.node.op.Add;
import com.kevin.mapping.script.node.op.Div;
import com.kevin.mapping.script.node.op.Minus;
import com.kevin.mapping.script.node.op.Mul;
import com.kevin.mapping.script.node.stmt.*;

import java.util.ArrayList;
import java.util.List;


public class Parser {

    private final Lexer lexer;
    private Token token = null;

    public Parser(Lexer lexer) {
        this.lexer = lexer;
        move();
    }

    public Stmt parser() {
        if (' ' == token.type) {
            return null;
        }
        if (token.type == '{') {
            return block();
        }
        return stmtList();
    }

    private Stmt stmtList() {
        StmtList ret = new StmtList(token);
        List<Stmt> list = new ArrayList<>();
        Stmt stmt = stmt();
        while (stmt != null) {
            list.add(stmt);
            stmt = stmt();
        }
        ret.setList(list);
        return ret;
    }

    private Stmt stmt() {
        if (' ' == token.type) {
            return null;
        }
        switch (token.type) {
            case '{':
                move();
                return block();
            case Type.IF:
                move();
                return ifStmt();
            case Type.DO:
                move();
                return doStmt();
            case Type.WHILE:
                move();
                return whileStmt();
            case Type.BREAK:
                return null;
        }

        Stmt bool = bool();
        if (null == token) {
            return bool;
        }
        if (token.type == '=') {
            move();
            return new AssignStmt(token, bool, bool());
        }
        return bool;
    }

    private Stmt bool() {
        Stmt right = join();
        if (token.type == Type.OR) {
            move();
            return new Or(Word.OR, right, bool());
        }
        return right;
    }

    private Stmt join() {
        Stmt equality = equality();
//        switch (token.type){
//            case Type.OR:
//                return new Or(Word.OR, equality, bool());
//            case Type.AND:
//                return new And(Word.AND, equality, join());
//            default:
//                return equality;
//        }

        if (token.type == Type.AND) {
            move();
            return new And(Word.AND, equality, join());
        }
        return equality;
    }

    private Stmt equality() {
        Stmt ret = rel();
        switch (token.type) {
            case Type.EQUAL:
                move();
                return new Equal(token, ret, equality());
            case Type.NE:
                move();
                return new Ne(token, ret, equality());
            default:
                return ret;
        }
    }

    private Stmt rel() {
        Stmt left = expr();
        switch (token.type) {
            case Type.LT:
                move();
                return new LessThan(token, left, expr());
            case Type.LTE:
                move();
                return new LessThanEqual(token, left, expr());
            case Type.GT:
                move();
                return new GreaterThan(token, left, expr());
            case Type.GTE:
                move();
                return new GreaterThanEqual(token, left, expr());
            default:
                return left;
        }
    }

    private Stmt expr() {
        Stmt left = term();
        switch (token.type) {
            case Type.ADD:
                move();
                return new Add(token, left, expr());
            case Type.MINUS:
                move();
                return new Minus(token, left, expr());
            default:
                return left;
        }
    }

    private Stmt term() {
        Stmt left = factor();
        move();
        switch (token.type) {
            case Type.MUL:
                return new Mul(token, left, term());
            case Type.DIV:
                return new Div(token, left, term());
            default:
                return left;
        }
    }

    private Stmt factor() {
        switch (token.type) {
            case '!':
                return factor();
            case '(':
                Stmt ret = bool();
                match(')');
                return ret;
            case Type.BOOLEAN:
                Word value = (Word) this.token;
                if (value.getValue(null).equals("true")) {
                    return new Stmt(BoolValue.TRUE);
                } else {
                    return new Stmt(BoolValue.FALSE);
                }

            default:
                return new Stmt(token);
        }
    }


    private Stmt whileStmt() {
        Stmt bool = bool();
        Stmt stmt = stmtList();
        return new WhileStmt(Word.WHILE, bool, stmt);
    }

    private Stmt doStmt() {
        Stmt stmt = stmtList();

        if (token.type != Type.WHILE) {
            throw new ParserException("expect while, but other word");
        }
        return new DoStmt(Word.WHILE, bool(), stmt);
    }

    private Stmt ifStmt() {
        Stmt bool = bool();
        Stmt stmt = stmtList();
        IfStmt ifStmt = new IfStmt(Word.WHILE, bool, stmt);
        if (token.type == Type.ELSE) {
            Stmt next = stmtList();
            ifStmt.setNext(next);
            return ifStmt;
        }
        return ifStmt;
    }


    private Stmt block() {
        Stmt ret = stmtList();
        match('}');
        return ret;
    }


    private void move() {
        token = lexer.scan();
    }

    private void match(char ch) {
        move();
        if (token.type != ch) {
            throw new ParserException("parser error.line=" + lexer.getLine());
        }
    }
}
